Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extra-natives/rdr3): Add natives for train tracks #2575

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Sage-of-Mirrors
Copy link

@Sage-of-Mirrors Sage-of-Mirrors commented Jun 3, 2024

Goal of this PR

This PR provides three new natives for RedM for working with train tracks:

  • GET_TRACK_COUNT: Returns the number of tracks that are currently loaded in the world.
  • GET_TRACK_FROM_INDEX: Returns the name hash of the track at the given index, or 0 if that index is invalid.
  • LOAD_TRACKS_FROM_FILE: Disposes of the currently-loaded tracks and loads new ones from a given traintracks.xml file. This allows for fully customized rail networks.

It also provides patches for three crashes that can occur when using LOAD_TRACKS_FROM_FILE:

  • Track junctions that point to invalid tracks will fall through the post-processing step, meaning that trains trying to use those junctions will use invalid data and thus cause a crash. The patch removes junctions with invalid track references prior to post-processing.
  • Clients can no longer call CREATE_MISSION_TRAIN() when there are 0 tracks loaded; the function returns a null train handle.
  • The train tracks exist in a pool with slots for 50 of them; the function for loading track data from the XML file now bails out if the track pool is full.

This resource can be used to test the track natives. See client.lua for the list of commands.

How is this PR achieving the goal

The extra natives can be found in extra-natives-rdr3/src/TrackNatives.cpp. The patches can be found in gta-core-rdr3/src/PatchTrainTrackCrashes.cpp.

GET_TRACK_COUNT and GET_TRACK_FROM_INDEX are just directly accessing the track data.

LOAD_TRACKS_FROM_FILE is based on FiveM's LOAD_WATER_FROM_PATH.

The patches for junction crashes and preventing trains from spawning when there are 0 tracks loaded are simple trampolines.

The patch for not loading more tracks than the game has space for is an ASM patch inserted at the original exit condition check for the XML loading loop.

This PR applies to the following area(s)

RedM, Natives

Successfully tested on

Game builds: 1491.50

Platforms: Windows

Checklist

  • Code compiles and has been tested successfully.
  • Code explains itself well and/or is documented.
  • My commit message explains what the changes do and what they are for.
  • No extra compilation warnings are added by these changes.

Fixes issues

Resolves #2424.

@github-actions github-actions bot added RedM Issues/PRs related to RedM triage Needs a preliminary assessment to determine the urgency and required action invalid Requires changes before it's considered valid and can be (re)triaged and removed triage Needs a preliminary assessment to determine the urgency and required action labels Jun 3, 2024
@prikolium-cfx
Copy link
Contributor

Hi. Thanks for your contribution. Can you please provide traintracks.xml for testing and describe expected behavior, so we can test it too?

@Sage-of-Mirrors
Copy link
Author

Sage-of-Mirrors commented Jun 18, 2024

Hi! Thank you for the response.

This resource, which I linked in the main PR message, can be used to test the changes.

Testing Steps

  1. In the F8 menu, run the command track_count. This should return the message "Track count: 29".
  2. In the F8 menu, run the command track_at_index 0. This should return the message "Track name hash at index 0: 3534397256".
  3. In the F8 menu, run the command track_at_index 51. This should return the message "Track name hash at index 51: 0".
  4. In the F8 menu, run the command load_tracks. This should return the following message:
Track count before load: 29
Track count after load: 34
  1. In the F8 menu, run the command track_count. This should return the message "Track count: 34".
  2. In the F8 menu, run the command spawn_trains. The game should not crash.
  3. Exit the game, and navigate to traintracks.xml, which is located at <resources>/tracktest/data/. Open traintracks.xml in a text editor, and remove the contents of the <train_tracks> XML element. Save the file, and re-enter the game. In the F8 menu, run the command load_tracks. This should return the following message:
Track count before load: 29
Track count after load: 0
  1. In the F8 menu, run the command spawn_trains. This should return the message "CreateMissionTrain() failed - track pool is empty!".
  2. Repeat Step 7, but instead of removing the contents of the <train_tracks> element, copy one of the <train_track> elements within it and paste it multiple times - enough to push the number of <train_track> elements within the <train_tracks> node over 50. Save the file, re-enter the game, and in the F8 menu, run the command load_tracks. This should return the following message:
Track count before load: 29
Track pool is full - no more tracks can be loaded.
Track count after load: 50

Please let me know if I can provide additional information. Thank you!

@Sage-of-Mirrors
Copy link
Author

I have updated my testing steps to include steps for testing having 0 train tracks in traintracks.xml, and having more than 50 tracks in it.

@Sage-of-Mirrors
Copy link
Author

Hey, just checking in. Is there anything I can do to help the review process along?

@github-actions github-actions bot added triage Needs a preliminary assessment to determine the urgency and required action and removed invalid Requires changes before it's considered valid and can be (re)triaged labels Jun 29, 2024
@prikolium-cfx prikolium-cfx added the enhancement Feature or other request that adds functionality or improved usability label Jul 1, 2024
@martonp96
Copy link

Hey @Sage-of-Mirrors,
Checked the code with your testing steps and it works as intended.
My only concern is reloading the tracks when trains are already spawned leads to game crash.
Other than that, its ok from me.

@Sage-of-Mirrors
Copy link
Author

Hey, thank you for the review!

I am aware of that crash; I did not address it in my original commit because it should only crash the client that calls LOAD_TRACKS_FROM_FILE. Would you like me to add a check in that native to prevent a reload if there are any trains in the level?

@martonp96
Copy link

Hey, thank you for the review!

I am aware of that crash; I did not address it in my original commit because it should only crash the client that calls LOAD_TRACKS_FROM_FILE. Would you like me to add a check in that native to prevent a reload if there are any trains in the level?

Yes please, at least people will know something went wrong when the new api is misused.

@Sage-of-Mirrors
Copy link
Author

While I was discussing this with @outsider31000, I was reminded that I had a different approach to this problem that involved an fxmanifest statement rather than natives: #2432. The PR was rejected by a previous member of the team.

Which method does Cfx feel is best? An fxmanifest statement seems much safer than a native that can be called at any point, since the statement would only be run at resource init. It would also get rid of the crashing-when-trains-exist issue altogether, since the reload only happens once, before any trains have been spawned.

@outsider31000
Copy link

Yeah I cant see any use case to reload tracks In runtime, even if there was one, it would still need to do it for all clients, doesnt seem a viable thing to do/allow it?
It should kinda be considered like a "ymap" load it once.
But that's just my opinion.

@prikolium-cfx
Copy link
Contributor

While I was discussing this with @outsider31000, I was reminded that I had a different approach to this problem that involved an fxmanifest statement rather than natives: #2432. The PR was rejected by a previous member of the team.

Which method does Cfx feel is best? An fxmanifest statement seems much safer than a native that can be called at any point, since the statement would only be run at resource init. It would also get rid of the crashing-when-trains-exist issue altogether, since the reload only happens once, before any trains have been spawned.

Just to be honest I liked previous solution more too. We need to think about it and maybe previous way will fit community needs better and in the same moment will be more stable

@martonp96
Copy link

Of course the optimal solution is to make it configurable on server side and load it once on connect, while checking for multiple loaded track files to avoid collision. I thought this was designed with runtime editing in mind.

@Sage-of-Mirrors
Copy link
Author

Of course the optimal solution is to make it configurable on server side and load it once on connect, while checking for multiple loaded track files to avoid collision. I thought this was designed with runtime editing in mind.

This native approach was suggested by disquse, who iirc felt that a manifest statement was not congruent with RedM's design principles and would be made obsolete when replacing the level meta became possible in RedM.

Perhaps a compromise can be reached: there is already a datafile loader for track data, TRAINTRACK_FILE. The original game code for the track loader does not attempt to dispose of the current track pool, it just adds the tracks to the existing pool and re-runs the post-processing functions on the whole pool. What if, instead of natives or a dedicated manifest statement, I modified the train file loader to dispose of the current track pool before loading the XML that was passed in? That way, a resource could just use datafile TRAINTRACK_FILE '<resource path>/data/traintracks.xml, and the tracks in the given file would replace the pool instead of add to what's already there.

Maybe there could be a server config boolean to explicitly turn on this behavior?

@Sage-of-Mirrors
Copy link
Author

The crux of the issue here, however, is that the track resources have to be distributed to the client.

Another idea: maybe a replicated fxserver convar that the client checks for before loading the default track data? traintrack_override_xml or something. Server owner would still need to package the track data as a resource to distribute it, though.

@outsider31000
Copy link

I would say that the best approach would be to keep it within the resource loading the tracks. A convar for this instance would not be ideal compared to using resource metadata in the fxmanifest.

@Sage-of-Mirrors
Copy link
Author

I have quite a bit of free time, so I will update the fxmanifest approach and add my crash patches to it.

Let me know what Cfx decides; I'm happy to finalize either method.

@Sage-of-Mirrors
Copy link
Author

Sage-of-Mirrors commented Jul 2, 2024

Here's the manifest version of this functionality: master...Sage-of-Mirrors:fivem:train-manifest-statement. This is what a resource using it would look like:

fx_version "cerulean"
game "rdr3"

rdr3_warning "I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships."

files {
  'data/traintracks.xml',
  'data/greatplains_jnctw_rdr1.dat',
  'data/greatplains_main_rdr1.dat',
  'data/mexico_jncte_rdr1.dat',
  'data/mexico_jnctw_rdr1.dat',
  'data/mexico_main_rdr1.dat',
  'data/trains_old_west_intersection01.dat',
  'data/trains_old_west_intersection02.dat',
  'data/trains_old_west01.dat',
  'data/trains_old_west02.dat',
  'data/trains_old_west03.dat',
}

replace_traintrack_file 'data/traintracks.xml'

If this is a better solution than the one presented in this PR, I can close it and make another for the manifest version.

@martonp96
Copy link

Here's the manifest version of this functionality: master...Sage-of-Mirrors:fivem:train-manifest-statement. This is what a resource using it would look like:

fx_version "cerulean"
game "rdr3"

rdr3_warning "I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships."

files {
  'data/traintracks.xml',
  'data/greatplains_jnctw_rdr1.dat',
  'data/greatplains_main_rdr1.dat',
  'data/mexico_jncte_rdr1.dat',
  'data/mexico_jnctw_rdr1.dat',
  'data/mexico_main_rdr1.dat',
  'data/trains_old_west_intersection01.dat',
  'data/trains_old_west_intersection02.dat',
  'data/trains_old_west01.dat',
  'data/trains_old_west02.dat',
  'data/trains_old_west03.dat',
}

replace_traintrack_file 'data/traintracks.xml'

If this is a better solution than the one presented in this PR, I can close it and make another for the manifest version.

@Sage-of-Mirrors
Yes, that looks much better.
@prikolium-cfx

@Sage-of-Mirrors
Copy link
Author

Any updates on which approach to this problem is best?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature or other request that adds functionality or improved usability RedM Issues/PRs related to RedM triage Needs a preliminary assessment to determine the urgency and required action
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[RedM] Cannot override base-game railway data
4 participants